package bancosys.tec.persist.validator;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeSet;

import jmine.tec.persist.PersistMessages;

import org.apache.commons.beanutils.BeanUtils;
import org.hibernate.Session;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.authorization.DisplayNameUtil;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.persist.hibernate.impl.annotations.AnnotationPartitionSessionFactory;

/**
 * @created 14/12/2007
 * @author Gustavo Almeida
 */
public class UniqueValidation {

    private final Map<String, Object> properties = new HashMap<String, Object>();

    private final Persistable bean;

    /**
     * Construtor. Cria uma nova validação de unicidade para uma determinada propriedade, método "getter" e bean.
     * 
     * @param property propriedade
     * @param method "getter" da propriedade
     * @param bean bean.
     */
    public UniqueValidation(String property, Method method, Persistable bean) {
        this.bean = bean;
        this.addProperty(property, method);
    }

    /**
     * Adiciona mais uma propriedade à validação.
     * 
     * @param property propriedade.
     * @param method método "getter"
     */
    public void addProperty(String property, Method method) {
        try {
            Object value = method.invoke(this.bean);
            this.properties.put(property, value);
        } catch (Throwable e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_VALIDATE.create(DisplayNameUtil.getBeanDisplayName(this.bean
                    .getClass())), e);
        }
    }

    /**
     * Metodo que tem a responsabilidade de verificar se os atributos unicos tem conteudo ou e nulo
     * 
     * @return verdadeiro, caso tenha valor sera retornado uma excessao para que a validacado unica seja efetuada
     */
    private boolean isValidUnique() {
        for (Object o : this.properties.values()) {
            if (o != null) {
                return true;
            }
        }

        return false;
    }

    /**
     * Valida unicidade para inserção.
     * 
     * @return erro de validação ou <code>null</code>
     */
    public ValidationError validateInsert() {
        if (this.isValidUnique()) {
            Collection<Persistable> results = this.findBeans();

            if (results.size() != 0) {
                Persistable old = results.iterator().next();
                return new ValidationError(PersistMessages.VALIDATION_ERROR_UNIQUE_VALIDATION_MESSAGE.create(
                        DisplayNameUtil.getBeanDisplayName(this.bean.getClass()), this.createErrorMessage(), this.getIdentification(old)));
            }
        }

        return null;
    }

    /**
     * Valida unicidade para alteração.
     * 
     * @return erro de validação ou <code>null</code>
     */
    public ValidationError validateUpdate() {
        if (this.isValidUnique()) {
            Collection<Persistable> results = this.findBeans();
            if (results.size() > 1) {
                Persistable old = results.iterator().next();
                return new ValidationError(PersistMessages.VALIDATION_ERROR_UNIQUE_VALIDATION_MESSAGE.create(
                        DisplayNameUtil.getBeanDisplayName(this.bean.getClass()), this.createErrorMessage(), this.getIdentification(old)));
            }
            if (results.size() == 0) {
                return null;
            }
            Persistable old = results.iterator().next();
            if (!old.getPk().equals(this.bean.getPk())) {
                return new ValidationError(PersistMessages.VALIDATION_ERROR_UNIQUE_VALIDATION_MESSAGE.create(
                        DisplayNameUtil.getBeanDisplayName(this.bean.getClass()), this.createErrorMessage(), this.getIdentification(old)));
            }
        }

        return null;
    }

    /**
     * Devolve a identificação do persistível passado.
     * 
     * @param p o persistível a ser identificado.
     * @return uma identificação única para o persisitível.
     */
    private String getIdentification(Persistable p) {
        return p.identification();
    }

    /**
     * Busca beans nas bases quente e fria a procura de violações de unicidade.
     * 
     * @return lista de beans encontrados
     */
    @SuppressWarnings("deprecation")
    private Collection<Persistable> findBeans() {
        BaseDAO<Persistable> dao = this.bean.getController().getDAOFactory().getGenericDAO(this.bean.getClass());
        Session session = this.bean.getController().getSessionFactory().getCurrentSession();
        Collection<Persistable> results;

        Persistable example = dao.createBean();

        try {
            for (String property : this.properties.keySet()) {
                BeanUtils.setProperty(example, property, this.properties.get(property));
            }
        } catch (Throwable e) {
            throw new PersistenceException(PersistMessages.UNABLE_TO_VALIDATE.create(DisplayNameUtil.getBeanDisplayName(this.bean
                    .getClass())), e);
        }

        if (this.bean.getPersister().isAuthorizable() && AnnotationPartitionSessionFactory.isAuthorizationFilterEnabled(session)) {
            AnnotationPartitionSessionFactory.disableAuthorizationFilter(session);
            try {
                results = dao.findByExample(example, true, "auth");
            } finally {
                AnnotationPartitionSessionFactory.enableAuthorizationFilter(session);
            }
        } else {
            results = dao.findByExample(example, true, "auth");
        }
        return results;
    }

    /**
     * Cria mensagem de erro.
     * 
     * @return mensagem de erro.
     */
    @SuppressWarnings("unchecked")
    private String createErrorMessage() {
        Class<Persistable> type = (Class<Persistable>) this.bean.getClass();
        Iterator<String> it = new TreeSet(this.properties.keySet()).iterator();

        StringBuilder sb = new StringBuilder("'");
        sb.append(DisplayNameUtil.getBeanPropertyDisplayName(type, it.next())).append("'");
        while (it.hasNext()) {
            sb.append(", '");
            sb.append(DisplayNameUtil.getBeanPropertyDisplayName(type, it.next()));
            sb.append("'");
        }
        return sb.toString();
    }
}
